feat(ooxml.js): record a pptx slide's reading order as a rank per shape - #1199
Open
fcbwilliams wants to merge 2 commits into
Open
feat(ooxml.js): record a pptx slide's reading order as a rank per shape#1199fcbwilliams wants to merge 2 commits into
fcbwilliams wants to merge 2 commits into
Conversation
fcbwilliams
commented
Sep 10, 2026
Member
Member
Author
|
For what it is worth, our own preference is to leave this opt-in rather than follow up by making it the default. The reason is the one the PR describes: making it default means reassigning So: no follow-up from us unless you would prefer the default. Entirely your call on what |
fcbwilliams
force-pushed
the
feat/pptx-reading-order
branch
from
September 10, 2026 15:21
ae24338 to
ce6b282
Compare
`readPptxContent` returns `ContentSlide.shapes` in `p:spTree` order, which is z-order -- roughly creation order -- and bears no relation to layout. That is fine for a consumer rendering the shapes, since each carries its own frame and is positioned independently. It stops being fine the moment a consumer reads a slide as prose: concatenating `shapes` in array order puts a column of bullets ahead of the heading that owns them, which the same deck exported to PDF does not do (a PDF renderer has already resolved layout to reading order). `orderShapesForReading` recovers that order by recursive XY-cut over the frames already on every shape: find a band of empty space no shape straddles, take the groups either side in order, recurse, and fall back to topmost-then-leftmost where a set overlaps on both axes. The axis is chosen per cut rather than always cutting rows first, which is what keeps a two-column slide readable -- and the gap is compared *relative* to the extent the shapes occupy on each axis, because a 16:9 slide is twice as wide as it is tall, so an absolute comparison reads a four-box grid down its columns instead of across its rows. Both layouts are covered by tests. Deliberately NOT applied inside `readPptxContent`. `ContentShape`'s `sourcePath` is assigned during the shape-tree walk as `slides[N].shapes[N]`, and the existing sourcePath tests assert it matches the shape's own array position -- so reordering the array in place would either desynchronise those paths from the positions they name, or redefine sourcePath away from the document order its comment promises. Exported instead, so a consumer reading a slide as prose can sort while one correlating by sourcePath keeps the order it has. Happy to make it the default and reassign sourcePath afterwards if you would rather -- that is your call about what sourcePath means, which is why this PR does not make it. No behaviour change for any existing consumer.
`readPptxContent` returns `ContentSlide.shapes` in `p:spTree` order, which is z-order -- roughly creation order -- and bears no relation to layout. That is fine for a consumer rendering the shapes, since each carries its own frame. It stops being fine for one reading a slide as prose: in spTree order a column of bullets can precede the heading that owns them, which the same deck exported to PDF does not do, because a PDF renderer has already resolved layout to reading order. `ContentShape` gains an optional `readingOrder`, recovered from the shapes' own geometry by recursive XY-cut: find a band of empty space no shape straddles, take the groups either side in order, recurse, and fall back to topmost-then-leftmost where a set overlaps on both axes. **A rank on the shape, not a reordered array**, expressed exactly as `paintOrder` already is -- including the same plain `z.number()`, for the same reason its comment gives: a fractional value can be inserted between two existing ones later. `sourcePath` is assigned as `slides[N].shapes[N]` and has to keep naming the position it names, so sorting the array would either desynchronise every path or redefine sourcePath away from the document order its own comment promises. The array is returned untouched; a consumer wanting reading order sorts by the rank, and one that does not is unaffected. Two details that took measuring, both covered by tests: The axis is chosen per cut rather than always cutting rows first, which is what keeps a two-column slide readable -- where each column is a heading above its own list, cutting rows yields both headings then both lists. On a title-above-body slide the same comparison comes out the other way round. The gap is compared *relative* to the extent the shapes occupy on each axis, because a 16:9 slide is nearly twice as wide as it is tall: an absolute comparison reads a four-box grid down its columns instead of across its rows. Additive and optional throughout, so no existing consumer changes behaviour. `ShapeDescriptor`'s hand-authored JSON Schema fragment is updated to match; the `ContentVector` variants deliberately are not, since they carry `paintOrder` but have no reading order of their own.
Mearman
force-pushed
the
feat/pptx-reading-order
branch
from
September 10, 2026 21:44
ce6b282 to
e5d2153
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.